#include "lib.h"

#include "pggame.h"
#include "pgconn.h"
#include "shue.h"
#include "pghand.h"
#include "jokcrdmg.h"
#include "pgsaved.h"
#include "optplyr.h"

PGPokerGame::PGPokerGame(PGPokerConnection* conn, int32 bet,
	int32 useCommission)
{
	this->conn = conn;
	this->bet = bet;
	this->useCommission = useCommission;

	dealer = PGHand::createHand(conn->getMachineID());
	player = PGHand::createHand(conn->getMachineID());
}

PGPokerGame::PGPokerGame(PGPokerConnection* conn, PGPokerSavedGame* saved)
{
	this->conn = conn;
	bet = 0;
	useCommission = true;

	dealer = PGHand::createHand(conn->getMachineID());
	player = PGHand::createHand(conn->getMachineID());

	// everything else is restored in restore()
	//
	restore(saved);
}

PGPokerGame::~PGPokerGame()
{
	delete dealer;
	delete player;
}


int32
PGPokerGame::numCardsToDeal()
{
	ASSERT( player->numCardsToDeal() == dealer->numCardsToDeal() );

	return player->numCardsToDeal();
}

/**
*deals the hand
*
*@param balance players balance in cents
*@return if deal is successful, returns true
*/
bool8
PGPokerGame::deal(int32 balance)
{
	Shue* shue;
	bool8 result = false;
	int32 i;

	ShueGameInfo sgi;
	sgi.gameId = GAMEID;
	sgi.machId = conn->getMachineID();
    #ifdef FORCEDSHOE 
       sgi.pid=conn->GetPID();
    #endif



	//
	// *DO NOT* change the number of decks!  Doing so will break the optimal
	// player and all code that compares hands to determine the winner.
	//
	shue = new Shue(dealer->getMgr(), 1, sgi);

	if (shue && dealer && player)
	{
		shue->shuffle();

        // kharmon.  10-16-06.  #8226.  Save the deck seed, not the cards.
        ShueSave save;
        shue->save(&save);
        m_nDeckSeed = save.seed;

		for (i = 0; i < numCardsToDeal(); i++)
		{
			player->add(shue->getCard());
			dealer->add(shue->getCard());
		}

		delete shue;
        
        if (conn->trackDeal(player))    // kharmon. 11-07-06.  #8226.  Save player cards on deal, but not dealer cards.
		{
			conn->msgStart();
			conn->msgAdd((uint8)'D');
			conn->msgAdd((uint8)(player->getCount() & 0xff));
			conn->msgReleaseBuf(player->out(conn->msgGetBuf()));
			conn->msgAdd(balance);
			conn->msgEnd();

			result = true;
		}
	}

	return result;
}
/**
*takes hand and splits off the two cards of player's choosing
*
*@param twocard the two cards of the smaller hand.
*@param balance the players balance in cents
*/
void
PGPokerGame::split(int32 twocard[2], int32 balance)
{
	if (twocard[0] >= 0 && twocard[0] < player->getCount() &&
		twocard[1] >= 0 && twocard[1] < player->getCount() &&
        twocard[0] != twocard[1] )
	{
		if (player->isSplitValid(twocard))
		{
			int32 balAdjust, payout, commission;
			Outcomes hiOutcome, loOutcome, outcome;
			int32 dealerTwoCard[2];
			int32 dJokerAs, pJokerAs;

			switch (conn->getMachineID())
			{
				case 0x00:
					{
						PGPokerHandRanks pRanks((PGPokerHand*)player, twocard);

						OptimalPlayer::split((PGPokerHand*)dealer, dealerTwoCard);

						PGPokerHandRanks dRanks((PGPokerHand*)dealer, dealerTwoCard);

						determineOutcomes(&dRanks, &pRanks,
							&hiOutcome, &loOutcome, &outcome);

						determineJokerAsRanks(&dRanks, &pRanks, &dJokerAs, &pJokerAs);
					}
					break;

				case 0x01:
					{
						PGTilesHandRanks pRanks((PGTilesHand*)player, twocard);

						OptimalPlayer::split((PGTilesHand*)dealer, dealerTwoCard);

						PGTilesHandRanks dRanks((PGTilesHand*)dealer, dealerTwoCard);

						determineOutcomes(&dRanks, &pRanks,
							&hiOutcome, &loOutcome, &outcome);
						
						determineJokerAsRanks(&dRanks, &pRanks, &dJokerAs, &pJokerAs);
					}
					break;

				default:
					conn->msgStart();
					conn->msgAdd((uint8)'N');
					conn->msgEnd();
					return;
			}

			balAdjust = calcPayout(outcome, &payout, &commission);

			if (conn->trackGameDone(payout, commission,
				dealerTwoCard, twocard, dJokerAs, pJokerAs,
				hiOutcome, loOutcome, outcome,
				balAdjust, &balance, dealer, player))       // kharmon.  10-16-06.  #8226.  Now safe to save the cards.
			{
				uint8 jokRankEncoded[8];
				int32 used;

				conn->msgStart();
				conn->msgAdd((uint8)'F');
				conn->msgAdd((uint8)(dealer->getCount() & 0xff));
				conn->msgReleaseBuf(dealer->out(conn->msgGetBuf()));
				conn->msgAdd((uint8)(dealerTwoCard[0] & 0xff));
				conn->msgAdd((uint8)(dealerTwoCard[1] & 0xff));

				used = 0;
				dealer->getMgr()->encode((uint8)(dJokerAs & 0xff), jokRankEncoded, &used);
				conn->msgAdd(jokRankEncoded[1]);

				used = 0;
				dealer->getMgr()->encode((uint8)(pJokerAs & 0xff), jokRankEncoded, &used);
				conn->msgAdd(jokRankEncoded[1]);

				conn->msgAdd(payout);
				conn->msgAdd(commission);
				conn->msgAdd((uint8)(hiOutcome & 0xff));
				conn->msgAdd((uint8)(loOutcome & 0xff));
				conn->msgAdd((uint8)(outcome & 0xff));
				conn->msgAdd(balance);
				conn->msgEnd();

				conn->gameDone();	// delete "this"
			}
			else	// tracker error
			{
				conn->msgStart();
				conn->msgAdd((uint8)'N');
				conn->msgEnd();
			}
		}
		else	// invalid player split
		{
			conn->msgStart();
			conn->msgAdd((uint8)'S');
			conn->msgEnd();
		}
	}
	else
	{
		conn->msgStart();
		conn->msgAdd((uint8)'S');
		conn->msgEnd();
	}
}
/**
*determines outcome of the game
*
*@param dr ranks of dealer's hands
*@param pr ranks of the player's hands
*@param hiOutcome the outcome of the 5 card hand
*@param loOutcome the outcome of the 2 card hand
*@param gameOutcome the outcome of the total game
*/
void
PGPokerGame::determineOutcomes(
	PGHandRanks* dr, PGHandRanks* pr,
	Outcomes* hiOutcome, Outcomes* loOutcome, Outcomes* gameOutcome)
{
	//
	// Compare each hand for winner then determine overall game winner
	// 
	switch (dr->compareLowRanks(pr))
	{
		case -1:
			*loOutcome = O_PLAYER_WON;
			break;
		case 0:
			*loOutcome = O_PUSH;
			break;
		case 1:
			*loOutcome = O_DEALER_WON;
			break;
	}

	switch (dr->compareHighRanks(pr))
	{
		case -1:
			*hiOutcome = O_PLAYER_WON;
			break;
		case 0:
			*hiOutcome = O_PUSH;
			break;
		case 1:
			*hiOutcome = O_DEALER_WON;
			break;
	}

	//
	// Game outcome is determined as follows:
	//		dealer wins all push hands (i.e. "copy" hands)
	//		if both player hands beat both dealer hands, the player wins
	//		if both dealer hands beat both player hands, the dealer wins
	//		otherwise the game is a push
	//
	if (*loOutcome == O_PUSH || *hiOutcome == O_PUSH)
	{
		if (*loOutcome == O_DEALER_WON || *hiOutcome == O_DEALER_WON ||
			*loOutcome == *hiOutcome)
			*gameOutcome = O_DEALER_WON;		// dealer wins all copy hands
		else
			*gameOutcome = O_PUSH;
	}
	else if (*loOutcome == *hiOutcome)
		*gameOutcome = *loOutcome;
	else
		*gameOutcome = O_PUSH;
}

void
PGPokerGame::determineJokerAsRanks(
	PGHandRanks* dr, PGHandRanks* pr,
	int32* dJokerAs, int32* pJokerAs)
{
	*dJokerAs = dr->getHighJokerAsRank();
	*pJokerAs = pr->getHighJokerAsRank();

	if (*dJokerAs == 0xff)
		*dJokerAs = CardMgr::ACE;

	if (*pJokerAs == 0xff)
		*pJokerAs = CardMgr::ACE;
}
/**
*calculates payout from game outcome
*
*@param gameOutcome the total outcome of the game
*@param payout the amount the player wins in cents negative for loss
*@param commission the comission amount in cents
*@return winnings for player
*/
int32
PGPokerGame::calcPayout(Outcomes gameOutcome, int32* payout, int32* commission)
{
	int32 winnings = 0;

	//
	// payout is calc'd as follows:
	//		> 0		winning bet (amt won, does *NOT* include orig bet)
	//		  0		push bet
	//		< 0		losing bet (amt lost)
	//

	switch (gameOutcome)
	{
		case O_DEALER_WON:
			*payout = -bet;
			*commission = 0;
			break;

		case O_PLAYER_WON:
			if (!useCommission)
			{
				*payout = bet;
				*commission = 0;
			}
			else
			{
				int32 dollars, cents;

				// 5% commission (round up to quarters)
				//
				dollars = (5 * bet) / 100;
				cents = dollars % 100;
				dollars -= cents;

				if (cents)
				{
					if (cents > 75)
					{
						dollars += 100;
						cents = 0;
					}
					else if (cents > 50)
						cents = 75;
					else if (cents > 25)
						cents = 50;
					else
						cents = 25;
				}

				*commission = dollars + cents;

				*payout = bet - *commission;
			}
			break;

		case O_PUSH:
			*payout = 0;
			*commission = 0;
			break;
	}

	winnings += bet + *payout;  // orig bet + winnings

	return winnings;
}
/**
*saves the game
*
*@param saved the game to save
*/
void
PGPokerGame::save(PGPokerSavedGame* saved)
{
	saved->useCommission = useCommission;

    // kharmon.  10-16-06.  #8226.  Save the deckseed to the database, so that we can
    // use it to regenerate the cards.  The "Deal" call no longer saves the actual cards.
    saved->bHasDeckSeed  = true;
    saved->nDeckSeed     = m_nDeckSeed;

	//
	// the rest is already stored in the database (no need to store the shue)
	//
}
/**
*restores a saved game for player
*
*@param saved the game to restore
*/ 
void
PGPokerGame::restore(PGPokerSavedGame* saved)
{
	bet = saved->bet;
	useCommission = saved->useCommission;

    if(saved->bHasDeckSeed==false)
    {
	    dealer->restore(saved->dealerCards, saved->numDealerCards);
	    player->restore(saved->playerCards, saved->numPlayerCards);
    }
    else
    {
        // kharmon.  10-16-06.  #8226.  Use the deck seed to regenerate the cards.
	    ShueGameInfo sgi;
	    sgi.gameId = GAMEID;
        sgi.machId = conn->getMachineID();
        
        Shue  *shue = new Shue(dealer->getMgr(), 1, sgi);

        ShueSave save;
        save.seed = saved->nDeckSeed;
        save.next = 0;
        shue->restore(&save);

		for (int32 i = 0; i < numCardsToDeal(); i++)
		{
			player->add(shue->getCard());
			dealer->add(shue->getCard());
		}

        delete shue;
    }

	//
	// At this point the server is *exactly* the same as when the game was
	// saved:  all automatic playing had already completed and the game
	// was idle.
	//
}
/**
*sends game to client
*/
void
PGPokerGame::send()
{
	//
	// This message is contained within another message
	// (don't call msgStart()/msgEnd()).
	//
	conn->msgAdd(bet);
	conn->msgAdd((uint8)(player->getCount() & 0xff));
	conn->msgReleaseBuf(player->out(conn->msgGetBuf()));
}
